#include "../Headers/registry.h"

static cl_list* g_registry_head;
static cl_list* g_registry_tail;
static pthread_mutex_t* g_registry_mutex;

// g_GarbageCollector will appear globally in this file, but it is also going to be an attribute of
// Program_cl's jobs list.
static cluster* g_GarbageCollector_cl;

void
registry_mutex_lock() {
    pthread_mutex_lock(g_registry_mutex);
}

void
registry_mutex_unlock() {
    pthread_mutex_unlock(g_registry_mutex);
}

cluster*
initialize_registry() {
    DEBUG("> initialize_registry()\n");

    g_registry_head = cl_list_new();
    g_registry_tail = g_registry_head;

    g_GarbageCollector_cl = cl_new();
    g_registry_mutex = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t));
    pthread_mutex_init(g_registry_mutex, NULL);

    DEBUG("< initialize_registry()\n");
    return g_GarbageCollector_cl;
}

cluster*
GarbageCollector_cl_invoke_func(cluster* cl) {
    info_dump_registry();
    return NULL;
}

/*!
 * @TODO This rough draft implementation will not include a mark and sweep system.
 */
/*
cluster*
GarbageCollector_cl_invoke_func(cluster* cl) {
    // notes:
    // 1) Sleep until something is appended to the jobs list.
    //      Use pthread_cond_wait for this. Couple the call with cl_remove or cl_dec_ref.
    // 2) When there are jobs, iterate through them. These jobs are clusters that need to be deleted from
    //    the registry.
    // 3) Check to see if registry is empty. If so, call destroy_func and exit program. If not, sleep, using
    //      pthread_cond_wait. Then return to step 2.
    cl_list *cursor;
    DEBUG("> GarbageCollector_cl_invoke_func()\n");
    cursor = cl->jobs;
    while (cursor) {
        cl_mutex_lock(cl);
        cl->jobs = cursor->next;
        cl_mutex_unlock(cl);

        --(cursor->cl->ref_count);
        if (!cursor->cl->ref_count)
            cl_unregister(cl);

        if (!g_registry_head) {
            // This is the GarbageCollector destroy func.
            cl->destroy_func(cl);
        }
    }
    DEBUG("< GarbageCollector_cl_invoke_func()\n");
    return cl;
}
*/

void
GarbageCollector_cl_destroy_func(cluster* cl) {
    DEBUG("> GarbageCollector_cl_destroy_func()\n");
    DEBUG("< GarbageCollector_cl_destroy_func()\n");
}

/*!
 * Creates a new note on REGISTRY and inserts this as the reference
 * in the tail node.
 */
void
cl_register(cluster* cl) {
    cl_mutex_lock(g_GarbageCollector_cl);
//    DEBUGF("Registering: %s\n", cl->name);

    if (g_registry_head->cl == NULL) {
        g_registry_head->cl = cl;
        ++cl->ref_count;
    } else {
        if (cl->ref_count == 0) {
            g_registry_tail->next = (cl_list*)malloc(sizeof(cl_list));
            g_registry_tail = g_registry_tail->next;
            g_registry_tail->cl = cl;
            g_registry_tail->next = NULL;
        }
        ++cl->ref_count;
    }
    cl_mutex_unlock(g_GarbageCollector_cl);
}

/*
void
cl_register_program(cluster* program) {
    DEBUG("> cl_register_program()\n");
    g_registry_head->cl = program;
    DEBUG("< cl_register_program()\n");
}
*/

void
cl_unregister(cluster* cl) {
    cl_list *cursor, *marker;
    int head_flag = 0;
    //! @TODO Evaluate how narrow a region the mutex lock can cover. For now, I'm erroring on the side of
    // caution.
    DEBUG("> cl_unregister()\n");
    registry_mutex_lock();
    cursor = g_registry_head;
    marker = cursor;

    if (cursor->cl == cl) {
        // This is Program_cl
        head_flag = 1;
        //free(cursor->cl);
    } else {
        while (cursor->cl != cl) {
            marker = cursor;
            cursor = cursor->next;
        }
    }

    // cursor now equals target cl. If it instead
    // equals NULL then there was a programming
    // problem somewhere.
    if (cursor == NULL) {
        DEBUG("!!! cl_unregister was called to free a cluster that "
              "was not in the registry!!!\n");
        return;
    }

    marker->next = cursor->next;
    if (g_registry_tail == cursor)
        g_registry_tail = marker;

    cl_invoke_destroy(cl);
    free(cursor->cl->mutex);
    free(cursor->cl);

    free(cursor);

    if (head_flag) {
        free(g_registry_head);
        g_registry_head = NULL;
        DEBUG("The head of the registry was deleted. Program should exit.\n");
    }
    registry_mutex_unlock();
    DEBUG("< cl_unregister()\n");
}

void
info_dump_registry() {
    DEBUG("***** info dump registry*********\n");
    info_dump_list(g_registry_head);
    DEBUG("***** end info dump registry*****\n");
}
